/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.spi;

import org.proteusframework.api.config.IConfigProperty;
import org.proteusframework.api.config.IConfigPropertyPrototype;
import org.proteusframework.api.config.IConfigurationDescriptor;
import org.proteusframework.api.model.*;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Base class used by every type of Proteus plugin provider that provides support for 1 or more configuration
 * properties that control the runtime behavior of the plugin.
 *
 * @author Tacoma Four
 */
public abstract class ProteusPluginFactory extends AbstractProvider implements IConfigurationDescriptor, IRegistrar
{
    /**
     * Collection of configuration property prototypes. These are only the prototypes, not the runtime values associated
     * with each configuration property.
     */
    protected Collection<IConfigPropertyPrototype> configProperties = new ArrayList<IConfigPropertyPrototype>();

    private Class implementationClass;

    protected Collection<IVersion> supportedVersions = new ArrayList<IVersion>();

    /**
     * Defines a plugin configuration provider with an empty list of configuration property prototypes.
     *
     * @param displayableName     Displayable name
     * @param implementationClass Implementation class
     */
    protected ProteusPluginFactory(IDisplayableName displayableName, Class implementationClass)
    {
        this(displayableName, implementationClass, new ArrayList<IConfigPropertyPrototype>());
    }

    /**
     * Defines a plugin configuration provider with the given list of configuration property prototypes.
     *
     * @param displayableName     Displayable name
     * @param implementationClass Implementation class
     * @param configProperties    Non-null list of
     *                            {@link org.proteusframework.api.config.IConfigPropertyPrototype} entries
     */
    protected ProteusPluginFactory(IDisplayableName displayableName, Class implementationClass,
                                   List<IConfigPropertyPrototype> configProperties)
    {
        super(displayableName);

        this.implementationClass = implementationClass;

        if (null != configProperties)
            this.configProperties.addAll(configProperties);
    }

    /**
     * Identifies a non-null collection of {@link org.proteusframework.api.config.IConfigPropertyPrototype}
     * that the plugin supports for varying its runtime behavior.
     *
     * @return Non-null collection of configuration property prototypes
     */
    @Override
    public Collection<IConfigPropertyPrototype> supportedProperties()
    {
        return configProperties;
    }

    @Override
    public <T extends IPlugin> T create(IVersionDescriptor objectVersionDescriptor, Class<T> expectedInterface) throws NoSuchVersionException
    {
        T plugin = null;
        try
        {
            //
            // By the time we get here, the Registrar Factory has vetted that the version is installed. However, we
            // need to vet the configuration property restrictions.
            //
            if (canHonorConfiguration(objectVersionDescriptor.configurationProperties()))
            {
                Object newInstance = implementationClass.newInstance();
                initializePlugin(newInstance, objectVersionDescriptor);

                plugin = expectedInterface.cast(Proxy.newProxyInstance(expectedInterface.getClassLoader(),
                        new Class[]{expectedInterface}, (InvocationHandler) newInstance));
            } else
            {
                throw new NoSuchVersionException(objectVersionDescriptor, "Unable to honor configuration property restrictions");
            }

        } catch (InstantiationException e)
        {
            e.printStackTrace();
        } catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }

        return plugin;
    }

    /**
     * Provides the opportunity for a concrete implementation to further initialize the plugin before it is wrapped
     * by a dynamic proxy representing the caller expected interface defined within the <code>IVersionDescriptor</code>.
     * <p/>
     * The default implementation has an empty body and does nothing to the new instance- there is no need to invoke
     * super.
     *
     * @param newInstance             New instance of the {@link #implementationClass}
     * @param objectVersionDescriptor Details about the requested creation call, including configuration properties
     * @throws NoSuchVersionException when the initialization cannot be performed properly
     */
    protected void initializePlugin(Object newInstance, IVersionDescriptor objectVersionDescriptor)
            throws NoSuchVersionException
    {
    }

    /**
     * Callback method for the factory to vet that it can honor the configuration restrictions requested for the
     * plugin instance.
     * <p/>
     * By default, returns true- there is no need to invoke super.
     *
     * @param configurationProperties Configuration property restrictions for the factory
     * @return true, if the factory can honor the mandatory configuration property restrictions
     */
    protected boolean canHonorConfiguration(Collection<IConfigProperty> configurationProperties)
    {
        return true;
    }

    @Override
    public boolean supportsVersion(IVersion version)
    {
        boolean found = false;

        for (IVersion curVersion : supportedVersions)
        {
            if (VersionCompatibility.Compatible == curVersion.isVersionCompatible(version))
            {
                found = true;
                break;
            }
        }

        return found;
    }

    public Collection<IVersion> supportedVersions()
    {
        Collection<IVersion> versionCollection = new ArrayList<IVersion>();
        versionCollection.addAll(supportedVersions);

        return versionCollection;
    }
}
